// Copyright 2021 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef CHROME_BROWSER_ASH_INPUT_METHOD_MULTI_WORD_SUGGESTER_H_
#define CHROME_BROWSER_ASH_INPUT_METHOD_MULTI_WORD_SUGGESTER_H_

#include "ash/services/ime/public/cpp/suggestions.h"
#include "base/time/time.h"
#include "chrome/browser/ash/input_method/suggester.h"
#include "chrome/browser/ash/input_method/suggestion_enums.h"
#include "chrome/browser/ash/input_method/suggestion_handler_interface.h"
#include "third_party/abseil-cpp/absl/types/optional.h"

namespace ash {
namespace input_method {

// Integrates multi word suggestions produced by the system with the assistive
// framework. Handles showing / accepting / dismissing any multi word
// suggestions generated by the system.
class MultiWordSuggester : public Suggester {
 public:
  // `suggestion_handler` and `profile` need to exist longer than the lifetime
  // of this object.
  MultiWordSuggester(SuggestionHandlerInterface* suggestion_handler,
                     Profile* profile);
  ~MultiWordSuggester() override;

  // Suggester overrides:
  void OnFocus(int context_id) override;
  void OnBlur() override;
  void OnExternalSuggestionsUpdated(
      const std::vector<ime::TextSuggestion>& suggestions) override;
  SuggestionStatus HandleKeyEvent(const ui::KeyEvent& event) override;
  bool Suggest(const std::u16string& text,
               size_t cursor_pos,
               size_t anchor_pos) override;
  bool AcceptSuggestion(size_t index = 0) override;
  void DismissSuggestion() override;
  AssistiveType GetProposeActionType() override;
  bool HasSuggestions() override;
  std::vector<ime::TextSuggestion> GetSuggestions() override;

  // Used to capture any changes to the current input text.
  void OnSurroundingTextChanged(const std::u16string& text,
                                size_t cursor_pos,
                                size_t anchor_pos);

 private:
  // Used to capture any internal state around the previously or currently
  // shown suggestions.
  class SuggestionState {
   public:
    enum State {
      kNoSuggestionShown,
      kPredictionSuggestionShown,
      kCompletionSuggestionShown,
      kTrackingLastSuggestionShown,
      kSuggestionDismissed,
      kSuggestionAccepted,
    };

    struct Suggestion {
      ime::TextSuggestionMode mode;
      std::u16string text;
      size_t confirmed_length;
      size_t initial_confirmed_length;
      base::TimeTicks time_first_shown;
      bool highlighted = false;
    };

    struct SurroundingText {
      std::u16string text;
      size_t cursor_pos;
      size_t prev_cursor_pos;
      bool cursor_at_end_of_text;
    };

    explicit SuggestionState(MultiWordSuggester* suggester);
    ~SuggestionState();

    // As the name suggests, used to update the current state and perform
    // any actions required during a transition.
    void UpdateState(const State& state);

    // Captures surrounding text context.
    void UpdateSurroundingText(const SurroundingText& surrounding_text);

    // Captures new suggestion context.
    void UpdateSuggestion(const Suggestion& suggestion);

    // Takes the current suggestion and surrounding text state, and ensures the
    // confirmed length or any other suggestion details are correct.
    void ReconcileSuggestionWithText();

    // Toggles the highlight state of the current suggeston.
    void ToggleSuggestionHighlight();

    // As the name suggests, is there a suggestion currently shown to the user?
    bool IsSuggestionShowing();

    // Is the user's cursor located at the end of the text they are currently
    // editing?
    bool IsCursorAtEndOfText();

    // Has the user highlighted the current suggestion showing?
    bool IsSuggestionHighlighted();

    // Returns the current suggestion state if there is any available.
    absl::optional<Suggestion> GetSuggestion();

    // Returns the last suggestion type shown to the user. This suggestion may,
    // or may not, be currently showing to the user.
    AssistiveType GetLastSuggestionType();

    // Resets the current suggestion context, and any other related state.
    void ResetSuggestion();

   private:
    // Not owned by this class
    MultiWordSuggester* suggester_;

    // The current state of the suggester (eg is a suggestion shown or not).
    State state_ = State::kNoSuggestionShown;

    // Last known surrounding text context captured by the suggester.
    absl::optional<SurroundingText> surrounding_text_;

    // The current suggestion shown to the user by the suggester.
    absl::optional<Suggestion> suggestion_;

    // The last suggestion type shown to the user.
    AssistiveType last_suggestion_type_ = AssistiveType::kGenericAction;
  };

  void DisplaySuggestionIfAvailable();
  void DisplaySuggestion(const SuggestionState::Suggestion& suggestion);
  void ResetSuggestionState();
  void ResetTextState();

  // Visibly highlight the suggestion in the ui shown to the user.
  void SetSuggestionHighlight(bool highlighted);

  // Announce the given message to the user.
  void Announce(const std::u16string& message);

  // The currently focused input (zero if none are focused)
  int focused_context_id_ = 0;

  // Not owned by this class
  SuggestionHandlerInterface* suggestion_handler_;

  // Current suggestion state
  SuggestionState state_;

  ui::ime::AssistiveWindowButton suggestion_button_;

  // The current user's Chrome user profile.
  Profile* const profile_;
};

}  // namespace input_method
}  // namespace ash

#endif  // CHROME_BROWSER_ASH_INPUT_METHOD_MULTI_WORD_SUGGESTER_H_
